--------------------------------------------------------------------------------
-- File:        i2c.vhd
-- Description: Simple I2C controller.
-- History:     Felix Bertram, 2002jan11, Created.
--------------------------------------------------------------------------------
 
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
 
entity ValI2C is
	port (
		-- clocks & reset
		clk24: in  std_logic;
		reset: in  std_logic;
		-- dynamic data from external units (token wrd)
		val:   in  std_logic_vector(7 downto 0); -- value
		idx:   out std_logic_vector(7 downto 0); -- index
		-- i2c bus
		sda:   out std_logic;
		scl:   out std_logic
		);
end ValI2C;
 
--------------------------------------------------------------------------------
architecture bhv of ValI2C is
	------------------------------------
	-- basic structure
	--     note: data field is used for constants or value-indices
	type i2cE is (start, stop, const, wrd);
	type i2cT is record
		tag:  i2cE;
		data: std_logic_vector(7 downto 0);
	end record;
 
	------------------------------------
	-- Crystal CS43L43 control sequence
	--     i2c start condition
	--         i2c address= 001000, R/nW= 0
	--             memory address pointer
	--             volume data
	--     i2c stop condition
	type seqT is array(0 to 9) of i2cT;
	constant seq: seqT:= (
	(start, x"00"),
	(const, "0010000"&'0'),
	(const, x"04"),  -- 04h Channel A Digital Volume Control
	(wrd,   x"00"),
	(stop,  x"00"),
	--
	(start, x"00"),
	(const, "0010000"&'0'),
	(const, x"05"),  -- 05h Channel B Digital Volume Control
	(wrd,   x"00"),
	(stop,  x"00")
	);
 
	------------------------------------
	-- clock divider and sequencing
	signal   idxSeq:         integer range seqT'low to seqT'high;
	signal   idxBit:         unsigned(3 downto 0);
	signal   idxCyc:         unsigned(1 downto 0);
	signal   clkdiv:         unsigned(8 downto 0);
	signal   clkp1a, clkp1b: std_logic;
	signal   clken:          std_logic;
	------------------------------------
	-- transmit data
	signal   txd:      std_logic_vector(8 downto 0);
	constant txdstart: std_logic_vector(8 downto 0):= "111000000"; -- I2C start pattern
	constant txdstop:  std_logic_vector(8 downto 0):= "000000111"; -- I2C stop  pattern
begin
 
	-- ic2 clock divider, approx 100kHz
	process(clk24, reset)
	begin
		if reset= '1' then
			clkdiv<= (others=> '0');
			clkp1b<= '0';
		elsif rising_edge(clk24) then
			clkdiv<= clkdiv + 1;
			clkp1b<= clkp1a;
		end if;
	end process;
	clkp1a<= clkdiv(clkdiv'left);
	clken <= clkp1a and not clkp1b;
 
	----------------------------------------------------------------------------
	-- create i2c timing
	----------------------------------------------------------------------------
 
	-- i2c clock cycle counter: 4 cycles per bit
	--           |___ _______________ ________
	--     sda   |___X_______________X________
	--           |        _______         ____
	--     scl   |_______/       \_______/
	--           |
	--     phase | 0 / 1 / 2 / 3 / 0 / 1 / 2 / ...
	process(clk24, reset)
	begin
		if reset= '1' then
			idxCyc<= "00";
		elsif rising_edge(clk24) then
			if clken= '1' then
				if idxCyc/= "11" then idxCyc<= idxCyc + 1;
				else                  idxCyc<= "00"; end if;
			end if;
		end if;
	end process;
 
	-- i2c bit counter: 9 bits per token (8 data + 1 ack)
	process(clk24, reset)
	begin
		if reset= '1' then
			idxBit<= x"8";
		elsif rising_edge(clk24) then
			if clken= '1' and idxCyc= 3 then
				if idxBit/= x"8" then idxBit<= idxBit + 1;
				else                  idxBit<= x"0"; end if;
			end if;
		end if;
	end process;
 
	-- i2c sequence counter: see seqT definition
	process(clk24, reset)
	begin
		if reset= '1' then
			idxSeq<= seqT'high;
		elsif rising_edge(clk24) then
			if clken= '1' and idxCyc= 3 and idxBit= 8 then
				if idxSeq/= seqT'high then 
					idxSeq<= idxSeq + 1;
				else                       
					idxSeq<= 0; 
				end if;
			end if;
		end if;
	end process;
 
	----------------------------------------------------------------------------
	-- create i2c clock and data
	----------------------------------------------------------------------------
 
	-- create data address
	idx<= seq(idxSeq).data;
 
	-- select byte to transfer, send out bits msb first
	process(clk24, reset)
	begin
		if reset= '1' then
			txd<= (others=> '1');
		elsif rising_edge(clk24) then
			if clken= '1' and idxCyc= 0 then
				if idxBit= 0 then
					case seq(idxSeq).tag is
						when start =>
							txd<= txdstart; 						
						when stop  =>
							txd<= txdstop; 						
						when const =>
							txd<= seq(idxSeq).data&'1'; -- const + ack 						
						when wrd   =>
							txd<= val&'1';              -- value + ack
					end case;
				else
					txd<= txd(7 downto 0) & '-';
				end if;
			end if;
		end if;
	end process;
	sda<= txd(8);
 
	-- select clock to transfer, send out clock
	process(clk24, reset)
	begin
		if reset= '1' then
			scl<= '1'; 		
		elsif rising_edge(clk24) then 			
		if clken= '1' then 				
		case seq(idxSeq).tag is 					
		
		when const | wrd=> 
			scl<= idxCyc(0) xor idxCyc(1); 					
		when start=>
			scl<= not(idxBit(3) or idxBit(2)); 					
		when stop=>
			scl<= idxBit(3) or idxBit(2);
		
		end case;
			end if;
		end if;
	end process;
 
end bhv;